/*
 * Copyright 2013 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <sys/time.h>

#define FALSE 0
#define TRUE  1

typedef struct
{
   // Handle to a program object
   GLuint programObject;

   // EGL handles
   EGLDisplay eglDisplay;
   EGLContext eglContext;
   EGLSurface eglSurface;

} UserData;

static void die(const char *msg) {
  printf("%s\n", msg);
  abort();
}

///
// Create a shader object, load the shader source, and
// compile the shader.
//
GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
   GLuint shader;
   GLint compiled;
   
   // Create the shader object
   shader = glCreateShader ( type );

   if ( shader == 0 )
   	return 0;

   // Load the shader source
   glShaderSource ( shader, 1, &shaderSrc, NULL );
   
   // Compile the shader
   glCompileShader ( shader );

   // Check the compile status
   glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );

   if ( !compiled ) 
   {
      GLint infoLen = 0;

      glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
      
      if ( infoLen > 1 )
      {
         char* infoLog = malloc(sizeof(char) * infoLen );

         glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
         printf ( infoLog );            
         
         free ( infoLog );
      }

      glDeleteShader ( shader );
      return 0;
   }

   return shader;

}

///
// Initialize the shader and program object
//
int Init ( UserData *userData )
{
  GLbyte vShaderStr[] =  
      "attribute vec4 vPosition;    \n"
      "void main()                  \n"
      "{                            \n"
      "   gl_Position = vPosition;  \n"
      "}                            \n";
   
   GLbyte fShaderStr[] =  
      "precision mediump float;\n"\
      "void main()                                  \n"
      "{                                            \n"
      "  gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n"
      "}                                            \n";

   GLuint vertexShader;
   GLuint fragmentShader;
   GLuint programObject;
   GLint linked;

   // Load the vertex/fragment shaders
   vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr);
   fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr);

   // Create the program object
   programObject = glCreateProgram ( );
   
   if ( programObject == 0 )
      return 0;

   glAttachShader ( programObject, vertexShader );
   glAttachShader ( programObject, fragmentShader );

   // Bind vPosition to attribute 0
   glBindAttribLocation(programObject, 0, "vPosition");

   // Link the program
   glLinkProgram ( programObject );

   // Check the link status
   glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );

   if ( !linked ) 
   {
      GLint infoLen = 0;

      glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );
      
      if ( infoLen > 1 )
      {
         char* infoLog = malloc(sizeof(char) * infoLen );

         glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
         printf( infoLog );
         
         free( infoLog );
      }

      glDeleteProgram ( programObject );
      return FALSE;
   }

   // Store the program object
   userData->programObject = programObject;

   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
  // glClearColor(1.0,0.0,0.0,0.5);
   return TRUE;
}



///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( UserData *userData )
{
  static GLfloat vVertices[] = {
    0.0f, 0.0f, 0.0f,
    -0.98f, -0.98f,0.0f,
    0.98f, -0.98f, 0.0f};
  
  vVertices[0] = (rand() % 101) / 100.0f * (rand() % 2==0 ? 1 : -1);
  // Set the viewport
  glViewport(0, 0, 320, 240);
  // printf("err:%x\n", glGetError());
  // Clear the color buffer
  glClear(GL_COLOR_BUFFER_BIT);

  // Use the program object
  glUseProgram(userData->programObject);

  static GLuint vbo;
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), vVertices,
               GL_STATIC_DRAW);
  // printf("err:%x\n", glGetError());
  // Load the vertex data
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  // printf("err:%x\n", glGetError());
  //  glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), vVertices,
  //  GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8,
  //  vVertices);
  glEnableVertexAttribArray(0);
  // printf("err:%x\n", glGetError());
  glDrawArrays(GL_TRIANGLES, 0, 3);

  eglSwapBuffers(userData->eglDisplay, userData->eglSurface);
}


GLuint compile_shader(GLenum shaderType, const char *src)
{
  GLuint shader = glCreateShader(shaderType);
  glShaderSource(shader, 1, &src, NULL);
  glCompileShader(shader);

  GLint isCompiled = 0;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
  if (!isCompiled)
  {
    GLint maxLength = 0;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
    char *buf = (char*)malloc(maxLength+1);
    glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
    printf("%s\n", buf);
    free(buf);
    return 0;
  }

   return shader;
}

GLuint create_program(GLuint vertexShader, GLuint fragmentShader)
{
   GLuint program = glCreateProgram();
   glAttachShader(program, vertexShader);
   glAttachShader(program, fragmentShader);
   glBindAttribLocation(program, 0, "apos");
   glBindAttribLocation(program, 1, "acolor");
   glLinkProgram(program);
   return program;
}

void esMainLoop(UserData *userData)
{
    struct timeval t1, t2;
    struct timezone tz;
    float deltatime;
    float totaltime = 0.0f;
    unsigned int frames = 0;
    gettimeofday ( &t1 ,  NULL);

    // while(userInterrupt(esContext) == GL_FALSE)
    while (1)
    {
        gettimeofday(&t2, &tz);
        deltatime = (float)(t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) * 1e-6);
        t1 = t2;

        // if (esContext->updateFunc != NULL)
        //     esContext->updateFunc(esContext, deltatime);
        // if (esContext->drawFunc != NULL)
        //     esContext->drawFunc(esContext);

        // eglSwapBuffers(esContext->eglDisplay, esContext->eglSurface);

        Draw(userData);
        totaltime += deltatime;
        frames++;
        if (totaltime >  0.0005f)
        {
            printf("%4d frames rendered in %1.4f seconds -> FPS=%3.4f\n", frames, totaltime, frames/totaltime);
            totaltime -= 2.0f;
            frames = 0;
            break;
        }
        // break;
    }
}

int main(int argc, char *argv[])
{
//   EmscriptenWebGLContextAttributes attr;
//   emscripten_webgl_init_context_attributes(&attr);
// #ifdef EXPLICIT_SWAP
//   attr.explicitSwapControl = 1;
// #endif
//   attr.majorVersion = 2;
//   EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx =
//       emscripten_webgl_create_context("#canvas", &attr);
//   assert(ctx && "Failed to create WebGL2 context");
//   emscripten_webgl_make_context_current(ctx);
//   EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//   assert(display != EGL_NO_DISPLAY);
//   assert(eglGetError() == EGL_SUCCESS);

// static const char vertex_shader[] =
//     "#version 100\n"
//     "attribute vec4 apos;"
//     "attribute vec4 acolor;"
//     "varying vec4 color;"
//     "void main() {"
//       "color = acolor;"
//       "gl_Position = apos;"
//     "}";
//   GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);

//   static const char fragment_shader[] =
//     "#version 100\n"
//     "precision lowp float;"
//     "varying vec4 color;"
//     "void main() {"
//       "gl_FragColor = color;"
//     "}";
//   GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);

//   GLuint program = create_program(vs, fs);
//   glUseProgram(program);

//   static const uint32_t pos_and_color[] = {
//   //  1,0,y,x,    a,b,g,r
//     0x400b36cd, 0xc00003ff,
//     0x400b3533, 0xc00ffc00,
//     0x4004cc00, 0xfff00000,
//   };

//   GLuint vbo;
//   glGenBuffers(1, &vbo);
//   glBindBuffer(GL_ARRAY_BUFFER, vbo);
//   glBufferData(GL_ARRAY_BUFFER, sizeof(pos_and_color), pos_and_color, GL_STATIC_DRAW);
//   glVertexAttribPointer(0, 4, GL_INT_2_10_10_10_REV, GL_TRUE, 8, 0);
//   assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_INT_2_10_10_10_REV failed");
//   glVertexAttribPointer(1, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 8, (void*)4);
//   assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_UNSIGNED_INT_2_10_10_10_REV failed");

//   glEnableVertexAttribArray(0);
//   glEnableVertexAttribArray(1);

//   glClearColor(0.3f,0.3f,0.3f,1);
//   glClear(GL_COLOR_BUFFER_BIT);
//   glDrawArrays(GL_TRIANGLES, 0, 3);

#ifdef EXPLICIT_SWAP
  emscripten_webgl_commit_frame();
#endif

//   return 0;
  int ret = EGL_TRUE;
  EGLint num_config;
  EGLContext g_egl_ctx;
  EGLDisplay g_egl_dpy;
  EGLConfig g_config;

  static const EGLint attribute_list[] = {
    EGL_RED_SIZE, 8,
    EGL_GREEN_SIZE, 8,
    EGL_BLUE_SIZE, 8,
    EGL_ALPHA_SIZE, 8,
    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
    EGL_NONE
  };

  static const EGLint context_attributes[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_NONE
  };

//   EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//   assert(display != EGL_NO_DISPLAY);
//   assert(eglGetError() == EGL_SUCCESS);
  g_egl_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (!g_egl_dpy)
    die("failed to create display");

  if (!eglInitialize(g_egl_dpy, NULL, NULL))
    die("failed to initialize egl");

  if (!eglChooseConfig(g_egl_dpy, attribute_list, &g_config, 1, &num_config))
    die("failed to choose config");

  g_egl_ctx = eglCreateContext(g_egl_dpy, g_config, EGL_NO_CONTEXT, context_attributes);
  if (!g_egl_ctx)
    die("failed to create context");

  printf("display:%x, config:%x, context:%x, %x\n", g_egl_dpy, g_config, g_egl_ctx, eglGetError());
  EGLNativeWindowType dummyWindow;
  EGLSurface surface = eglCreateWindowSurface(g_egl_dpy, g_config, dummyWindow, NULL);
  if (!surface)
    die("failed to create window surface");

  if (!eglMakeCurrent(g_egl_dpy, surface, surface, g_egl_ctx))
    die("failed to activate context");
  
  assert(eglGetError() == EGL_SUCCESS);
  assert(ret == EGL_TRUE);
  assert(eglGetCurrentContext() == g_egl_ctx);
  assert(eglGetCurrentSurface(EGL_READ) == surface);
  assert(eglGetCurrentSurface(EGL_DRAW) == surface);

  UserData userData;
  userData.eglDisplay = g_egl_dpy;
  userData.eglSurface = surface;

  ret = Init(&userData);
  // printf("ret:%x\n", ret); 
  
  static const uint32_t pos_and_color[] = {
  //  1,0,y,x,    a,b,g,r
    0x400b36cd, 0xc00003ff,
    0x400b3533, 0xc00ffc00,
    0x4004cc00, 0xfff00000,
  };

  esMainLoop(&userData);
  // Draw(&userData);
// while (1)
// {
//   GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f, 
//                         -0.5f, -0.5f, 0.0f,
//                         0.5f, -0.5f, 0.0f };

//   glViewport(0, 0, 320, 240);
//   glClear ( GL_COLOR_BUFFER_BIT );
//   glUseProgram(userData.programObject);

//   GLuint vbo;
//   glGenBuffers(1, &vbo);
//   glBindBuffer(GL_ARRAY_BUFFER, vbo);
//   glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), vVertices, GL_STATIC_DRAW);
//   // printf("err:%x\n", glGetError());
//   // Load the vertex data
//   glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
//   // printf("err:%x\n", glGetError());
  
//   // // printf("err:%x\n", glGetError());
//   glEnableVertexAttribArray ( 0 );

//   glDrawArrays(GL_TRIANGLES, 0, 3);

//   eglSwapBuffers(userData.eglDisplay, userData.eglSurface);
// }

  // eglSwapBuffers ( userData.eglDisplay, userData.eglSurface);
  // printf("sp:%s\n", glGetString(GL_EXTENSIONS)); 
  // glEnableVertexAttribArray(0);
  // glEnableVertexAttribArray(1);

  // glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8, 0);
  // // printf("err:%x\n", glGetError());
  // glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8, (void*)4);
  // // printf("err:%x\n", glGetError());
  // assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_UNSIGNED_INT_2_10_10_10_REV failed");
  
  // glClearColor(0.3f,0.9f,0.3f,1);
  // glClear(GL_COLOR_BUFFER_BIT);
  
  // glClearColor(1.0,0.0,0.0,0.5);

  
  return 0;
}
